# 下载英文视频
# 下载中文字幕
# 解析 JSON 字幕文件
# 生成字幕和文本文件
# 生成字幕音频文件
# 获取音频时长并保存
# 更新字幕文件
# 处理音频文件速度

import subprocess
import os # 导入操作系统模块，用于文件和目录操作
import glob
import time
import random

import json
import requests
import ffmpeg
import re # 导入正则表达式模块，用于字符串匹配
import shutil

from pydub import AudioSegment  # 从 pydub 导入 AudioSegment，用于音频处理


# 全局配置
PROXY = "http://127.0.0.1:10809"
COOKIES_FILE = r"D:\Downloads\www.youtube.com_cookies.txt"
VIDEO_URL = "https://www.youtube.com/watch?v=GWGbOjlJDkU"
OUTPUT_FILE = 'zh-Hans.json' # 中文字幕json
language = 'zh-Hans'

# 路径配置
output_subtitles_file = 'ch.txt' # 带时间戳中文字幕
output_texts_file = 'chdoc.txt' # 纯文本中文字幕
audio_durations_file = 'audio_durations.txt' # 读取中文音频时长
output_file = 'updated_ch.txt' # 要读取的 ch.txt 文件
OUTPUT_FOLDER = 'output_wav_files'
processed_folder = 'processed_wav_files' # 已处理的 WAV 文件夹路径
unprocessed_folder = 'unprocessed_wav_files' # 未处理的 WAV 文件夹路径
final_output_file = 'ch.wav'  # 中文配音

input_file = 'English.mp4' # 英文原视频
silent_video = 'silent_video.mp4' # 静音视频
output_video = 'audio_added.mp4' # 最终中文配音视频


# 创建必要的文件夹
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
os.makedirs(processed_folder, exist_ok=True)
os.makedirs(unprocessed_folder, exist_ok=True)


# 实时运行命令并获取输出
def run_command_real_time(command):
    saveout = ''  # 初始化保存输出的字符串
    try:
        process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)  # 启动外部命令并允许获取标准输出和错误

        while True:  # 持续读取输出
            output = process.stdout.readline()  # 逐行读取输出
            if output == '' and process.poll() is not None:  # 如果没有输出且进程已结束，则退出循环
                break
            if output:  # 如果有输出
                print(output.strip())  # 打印当前输出行
                saveout += output.strip() + '\n'  # 将输出保存到字符串中

        return_code = process.poll()  # 获取命令的返回值
        if return_code != 0:  # 如果返回值不为0，表示命令执行失败
            error_output = process.stderr.read()  # 读取标准错误输出
            print(f"命令执行失败，返回码: {return_code}")  # 打印返回码
            print(f"错误信息: {error_output.strip()}")  # 打印错误信息

    except Exception as e:  # 捕获异常
        print(f"发生错误: {e}")  # 打印错误信息
    return saveout  # 返回保存的输出

# 下载字幕
def download_subtitle():
    list_command = [  # 定义列出字幕的命令
        "yt-dlp",
        "--list-subs",
        "--proxy", PROXY,  # 使用配置的代理
        "--cookies", COOKIES_FILE,  # 使用配置的 cookies 文件
        VIDEO_URL  # 使用配置的视频链接
    ]

    res = run_command_real_time(list_command)  # 执行命令并获取输出
    reslines = res.split('\n')  # 将输出按行分割成列表
    ch = ''  # 初始化用于存储字幕信息的变量

    for i, v in enumerate(reslines):  # 遍历每行输出
        if 'Chinese (Simplified)' in v:  # 查找包含简体中文的行
            ch = v  # 保存找到的行

    # 只打印找到的简体中文字幕信息
    if ch:
        print('-' * 20)  
        print(ch)  # 打印找到的简体中文字幕信息
        print('-' * 20)  
    else:
        print("未找到简体中文字幕信息。")  # 如果没有找到，则打印信息


    # 生成随机的延迟时间，范围为 2 到 10 之间的浮点数
    delay_time = random.uniform(4, 10)
    print(f"等待 {delay_time:.2f} 秒...")  # 打印等待时间
    time.sleep(delay_time)  # 随机延迟

    if True:  # 始终为真，执行以下代码
        download_command = [  # 定义下载字幕的命令
            "yt-dlp",
            "--skip-download",  # 跳过视频下载
            "--write-auto-subs",  # 写入自动生成的字幕
            "--sub-langs", language,  # 设置下载的字幕语言为简体中文
            "--sub-format", "json3",  # 设置字幕格式为 json3
            "--proxy", PROXY,  # 使用配置的代理
            "--cookies", COOKIES_FILE,  # 使用配置的 cookies 文件
            VIDEO_URL  # 使用配置的视频链接
        ]

        download_result = run_command_real_time(download_command)  # 执行下载命令并获取输出

        downloaded_files = glob.glob('*.*.json3')  # 查找当前目录下所有符合条件的文件
        if downloaded_files:  # 如果找到了文件
            original_file = downloaded_files[0]  # 假设只下载一个文件，取第一个
            os.rename(original_file, OUTPUT_FILE)  # 重命名下载的文件
            print(f"下载并保存的文件名: {OUTPUT_FILE}")  # 打印保存的文件名

            if os.path.exists(OUTPUT_FILE):  # 检查文件是否存在
                print(f"文件已保存为: {OUTPUT_FILE}")  # 打印保存的文件路径
            else:
                print("下载失败，未能保存文件。")  # 打印失败信息
        else:
            print("没有找到下载的字幕文件。")  # 如果没有找到文件，打印信息


#从字幕片段列表中提取字幕文本和时间偏移信息。
def getTxt(dat):
    out = ''  # 存储所有字幕文本
    datlist = []  # 存储时间偏移和字幕文本
    for i, v in enumerate(dat):  # 遍历字幕片段列表
        tstr = v['utf8']  # 获取当前字幕文本
        out += tstr  # 将字幕文本添加到总字符串中
        offset = 0  # 初始化时间偏移
        if i != 0:
            offset = v['tOffsetMs']  # 如果不是第一个字幕片段，获取其时间偏移
        datlist.append([offset, tstr])  # 将时间偏移和字幕文本添加到列表中
    out = out.replace('\n','')
    return [out, datlist] 


# 解析 JSON 字幕文件
def decodeJosn(fname):
    jdict = None  # 初始化字典
    with open(fname, 'rb') as f:  # 以二进制模式打开文件
        jdict = json.load(f)  # 读取并解析 JSON 数据

    subtxts = jdict['events']  # 提取字幕事件列表
    alltime = 0  # 初始化总时间
    dats = {}  # 初始化字典以存储字幕事件信息
    outstrs = {}  # 初始化字典以存储非空字幕文本

    for i, v in enumerate(subtxts):  # 遍历每个字幕事件
        if i == 0:
            alltime = v['dDurationMs']  # 记录第一个事件的持续时间
        else:
            st = v['tStartMs']  # 获取开始时间
            dt = 0  # 初始化持续时间
            if 'dDurationMs' in v:
                dt = v['dDurationMs']  # 如果有持续时间，则获取

            if 'segs' in v:  # 如果有字幕片段
                txtdat = getTxt(v['segs'])  # 调用函数获取字幕文本和时间偏移

            dats[i] = [st, dt, txtdat[0], txtdat[1]]  # 存储事件相关信息
            if txtdat[0] != '\n' and len(txtdat[0]) > 0:  # 如果字幕文本不为空
                outstrs[i] = [st, dt, txtdat[0]]  # 存储非空字幕文本
    return dats, outstrs  # 返回两个字典

#  生成字幕和文本文件
def generate_text_files(dats, outstrs):
    with open(output_subtitles_file, 'w', encoding='utf-8') as f:
        for k, v in outstrs.items():
            f.write(f"{k}: {v}\n")
    print(f"字幕信息已保存到 '{output_subtitles_file}' 文件中。")

    subtitle_texts = [v[2] for v in outstrs.values()]  # 提取每个字幕文本 
   
    with open(output_texts_file, 'w', encoding='utf-8') as f:
        f.write('\n'.join(subtitle_texts))
    print(f"提取的字幕文本已保存到 '{output_texts_file}' 文件中。")


# 定义下载音频的函数
def getAudio(savename, url):
    response = requests.get(url)
    if response.status_code == 200:
        with open(savename, "wb") as file:
            file.write(response.content)
        print(f"音频文件 {savename} 已成功下载到本地。")
        return True
    else:
        print(f"下载失败，状态码: {response.status_code}")
        return False

# 使用 ChatTTS 生成音频
def chattts(msg, audioname):
    res = requests.post('http://192.168.88.251:9966/tts', data={
        "text": msg,
        "prompt": "",
        "voice": "3333",
        "temperature": 0.3,
        "top_p": 0.7,
        "top_k": 20,
        "skip_refine": 0,
        "custom_voice": 0,
        "is_split": 1
    })
    if res.status_code == 200:
        url = res.json().get('url')
        if url:
            getAudio(audioname, url)
        else:
            print("未能获取音频文件的 URL。")
    else:
        print(f"请求失败，状态码: {res.status_code}")

# 生成字幕音频文件
def process_subtitles(output_texts_file, OUTPUT_FOLDER):
    with open(output_texts_file, 'r', encoding='utf-8') as file:
        lines = file.readlines()
    for index, line in enumerate(lines):
        line = line.strip()
        if line:
            audioname = f'{OUTPUT_FOLDER}/line_{index + 1}.wav'
            chattts(line, audioname)

# 获取音频时长并保存
def get_audio_durations(OUTPUT_FOLDER, audio_durations_file):
    audio_durations = []
    for filename in os.listdir(OUTPUT_FOLDER):
        if filename.endswith('.wav') and filename.startswith('line_'):
            match = re.search(r'line_(\d+)', filename)
            if match:
                index = int(match.group(1))
                file_path = os.path.join(OUTPUT_FOLDER, filename)
                try:
                    probe = ffmpeg.probe(file_path)
                    duration_sec = float(probe['format']['duration'])
                    duration_ms = int(duration_sec * 1000)
                    audio_durations.append((index, filename, duration_ms))
                except ffmpeg.Error as e:
                    print(f"无法处理文件 {filename}: {e}")
    audio_durations.sort()
    with open(audio_durations_file, 'w') as f:
        for _, filename, duration_ms in audio_durations:
            f.write(f"{filename}: {duration_ms} ms\n")
    print(f"音频时长已写入 {audio_durations_file}")

# 更新字幕文件
def update_ch_file(output_subtitles_file, audio_durations_file, output_file):
    durations = {}
    with open(audio_durations_file, 'r', encoding='utf-8') as f:
        for line in f:
            match = re.match(r'line_(\d+).wav: (\d+) ms', line.strip())
            if match:
                index = int(match.group(1))
                duration = int(match.group(2))
                durations[index] = duration
    updated_lines = []
    with open(output_subtitles_file, 'r', encoding='utf-8') as f:
        for line in f:
            match = re.match(r'(\d+): \[(.*)\]', line.strip())
            if match:
                index = int(match.group(1))
                if index in durations:
                    updated_content = f"{match.group(2)}, {durations[index]}"
                    updated_line = f"{index}: [{updated_content}]\n"
                    updated_lines.append(updated_line)
    with open(output_file, 'w', encoding='utf-8') as f:
        f.writelines(updated_lines)
    print(f"更新后的内容已写入 {output_file}")

# 提取第1,3,4数值的函数
def extract_values(line):
    match = re.search(r'(\d+): \[(\d+), (\d+), .*?, (\d+)\]', line)
    if match:
        first_value = int(match.group(1))
        third_value = int(match.group(3))
        fourth_value = int(match.group(4))
        return first_value, third_value, fourth_value
    return None, None, None


# 处理音频文件速度
def process_audio(index, third_value, fourth_value):
    # 构造对应的音频文件名
    audio_file = os.path.join(OUTPUT_FOLDER, f"line_{index}.wav")
    if os.path.exists(audio_file):
        if fourth_value > third_value:
            # 使用 FFmpeg 进行音频变速处理
            output_file = os.path.join(processed_folder, f"line_{index}.wav")
            speed_factor = fourth_value / third_value
            try:
                # 调用 FFmpeg 进行音频处理
                ffmpeg.input(audio_file).filter('atempo', speed_factor).output(output_file).run(overwrite_output=True)
                print(f"Processed: {audio_file} to {output_file} with speed factor {speed_factor:.2f}")
            except ffmpeg.Error as e:
                print(f"Error processing {audio_file}: {e}")
        else:
            # 直接复制未处理的音频到 unprocessed 文件夹
            output_file = os.path.join(unprocessed_folder, f"line_{index}.wav")
            shutil.copy(audio_file, output_file)
            print(f"Unprocessed: {audio_file} copied to {unprocessed_folder}")
    else:
        print(f"File not found: {audio_file}")


# 提取第2个数值的函数
def extract_second_value(line):
    match = re.search(r'(\d+): \[(\d+), (\d+), .*?, (\d+)\]', line)
    return int(match.group(2)) if match else None

# 读取 ch.txt 文件并提取第二个数值
def read_ch_file():
    with open(output_file, 'r', encoding='utf-8') as f: 
        return [extract_second_value(line) for line in f if extract_second_value(line) is not None]

# 收集所有 WAV 文件
def collect_wav_files():
    wav_files = [] 
    # 遍历处理和未处理的文件夹
    for folder in [processed_folder, unprocessed_folder]:
        for filename in os.listdir(folder):  # 列出文件夹中的所有文件
            if filename.endswith('.wav'):  # 如果文件是 WAV 文件
                index = int(filename.split('_')[1].split('.')[0])  # 提取文件名中的索引
                wav_files.append((index, os.path.join(folder, filename)))  # 将索引和路径元组添加到列表中
    return sorted(wav_files)  # 按索引排序并返回


# 根据切入时间点连接音频
def concatenate_audio_with_silence(wav_files, second_values):
    combined = AudioSegment.silent(duration=0)  # 创建一个初始的静音音频段

    # 遍历所有 WAV 文件和对应的切入时间点
    for i, (index, file_path) in enumerate(wav_files):
        audio_segment = AudioSegment.from_wav(file_path)  # 读取当前 WAV 文件
        start_time_ms = second_values[i]  # 获取对应的切入时间点（单位：ms）

        # 添加静音段
        if i > 0:  # 如果不是第一个音频
            silence_duration = max(0, start_time_ms - len(combined))  # 计算所需的静音时长
            combined += AudioSegment.silent(duration=silence_duration)  # 添加静音段

        combined += audio_segment  # 将当前音频段添加到组合音频中

    combined.export(final_output_file, format='wav')  # 将组合的音频导出为 WAV 文件
    print(f"Final audio exported to {final_output_file}")  # 输出导出信息


if __name__ == "__main__":
    
# 下载视频
    command = [
        "D:\\tool\\video\\yt-dlp.exe",  # 提供 yt-dlp 的完整路径
        "-f", "bv[ext=mp4]+ba[ext=m4a]",
        "--cookies", "d:\\Downloads\\www.youtube.com_cookies.txt",
        "-S", "codec:h264:m4a",
        VIDEO_URL 
    ]

    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    print(result.stdout)  # 显示命令输出
    print(result.stderr)  # 如果有错误，显示错误信息
    print('-'*10)
    
# 查找下载的视频文件并重命名为 English.mp4
    downloaded_files = glob.glob('*.mp4')  # 查找当前目录下的 mp4 文件
    if downloaded_files:
        original_file = downloaded_files[0]  # 假设只有一个视频文件
        os.rename(original_file, 'English.mp4')  # 重命名为 English.mp4
        print(f"视频文件已重命名为: English.mp4")
    else:
        print("未找到下载的视频文件。")

# 下载字幕
    download_subtitle() 

#解析json字幕，chattts生成音频，读取音频时长，得到updated_ch.txt对比时间文档

    dats, outstrs = decodeJosn(OUTPUT_FILE)  # 解析 JSON 字幕文件
    generate_text_files(dats, outstrs) #  生成字幕和文本文件
    process_subtitles(output_texts_file, OUTPUT_FOLDER)  # 处理字幕文件并生成音频
    get_audio_durations(OUTPUT_FOLDER, audio_durations_file)  # 获取音频时长并保存
    update_ch_file(output_subtitles_file, audio_durations_file, output_file) # 更新字幕文件
    
# 处理音频速度

    with open(output_file, 'r', encoding='utf-8') as f:
        lines = f.readlines()
    # 遍历每一行，使用 enumerate 获取索引和行内容
    for index, line in enumerate(lines, start=1):
        first_value, third_value, fourth_value = extract_values(line)
        if third_value is not None and fourth_value is not None:
            print(f"第{first_value}行，第二个数值: {third_value}, 第四个数值: {fourth_value}")
            process_audio(index, third_value, fourth_value)

# 获取中文音频

    all_second_values = read_ch_file()  # 读取 ch.txt 文件并提取所有第二个数值（音频切入时间点）
    wav_files = collect_wav_files()  # 收集所有 WAV 文件
    concatenate_audio_with_silence(wav_files, all_second_values)  # 根据切入时间连接音频

# 合成中文配音视频

    ffmpeg.input(input_file).output(silent_video, an=None).run()
    ffmpeg.input(silent_video).output(ffmpeg.input(final_output_file), output_video, vcodec='copy', acodec='aac', shortest=None).run()
    print("音频添加完成，输出文件为：", output_video)